package org.longteng.security.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.longteng.security.jwtkeys.JwtECDSAKey;
import org.longteng.security.jwtkeys.JwtKeyPair;
import org.longteng.security.jwtkeys.JwtRSAKey;

import javax.crypto.Cipher;
import java.io.File;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.util.*;

/**
 * token的密钥生成
 * @author hymn.com
 * @date 2022/12/07 10:00:00
 */
@Slf4j
public class SecretKeyUtils {

    /**
     * 生成加密数据Sign
     * @param data
     * @param secret
     * @return
     */
    public static String markSecretSign(String data, String secret) {
        String jwtSign = JWT.create()
                .withClaim("data", data)
                .sign(Algorithm.HMAC256(secret));
        if (log.isDebugEnabled()) {
            log.debug("生成加密数据Sign:{}",jwtSign);
        }
        return getShortUrl(jwtSign);   // 生成的sign变短算法
    }

    /**
     * 验证Sign, HMAC256方式
     * @param data
     * @param secret
     * @param checkSign
     * @return
     */
    public static boolean verifySign(String data, String secret, String checkSign) {
        if (log.isDebugEnabled()) {
            log.debug("验证Sign,data:{},secret:{},checkSign:{}", data,secret,checkSign);
        }
        String jwtSign = markSecretSign(data, secret);
        boolean verifyFlag = Optional.ofNullable(jwtSign).orElse("").equals(checkSign);
        if (log.isDebugEnabled()) {
            log.debug("验证Sign结果,verifyFlag:{},\n  markSign:{}\n checkSign:{}", verifyFlag, jwtSign, checkSign);
        }
        if(! verifyFlag) {
            log.warn("生成的Sign与传入checkSign不一致！");
        }
        return Optional.ofNullable(jwtSign).orElse("").equals(checkSign);
    }


    /***
     * SHA加码 生成32位加密码
     */
    public static String stringToSHA(String inStr) {
        MessageDigest encSHA = null;
        try {
            encSHA = MessageDigest.getInstance("SHA-256");
        } catch (Exception e) {
            log.error("生成32位SHA码错误.", e);
            return "";
        }
        char[] charArray = inStr.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++) {
            byteArray[i] = (byte) charArray[i];
        }
        byte[] SHABytes = encSHA.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < SHABytes.length; i++) {
            int val = ((int) SHABytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    /**
     * 短网址生成方法
     * @param url
     * @return
     */
    private static String[] shortUrl(String url, String setKey) {
        // 可以自定义生成 SHA 加密字符传前的混合 KEY
        String key = StringUtils.isNotBlank(setKey) ? setKey
                : System.currentTimeMillis() + UUID.randomUUID().toString(); //混淆key,加上当前时间,并且取一个随机字符串
        // 要使用生成 URL 的字符
        String[] chars = new String[] { "a", "b", "c", "d", "e", "f", "g", "h",
                "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
                "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
                "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
                "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
                "U", "V", "W", "X", "Y", "Z"
        };

        // 对传入网址进行 SHA 加密
        String sSHAEncryptResult = stringToSHA(key + url);
        String hex = sSHAEncryptResult;
        String[] resUrl = new String[4];
        for (int i = 0; i < 4; i++) {
            // 把加密字符按照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算
            String sTempSubString = hex.substring(i * 8, i * 8 + 8);
            // 这里需要使用 long 型来转换，因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用
            // long ，则会越界
            long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);

            String outChars = "";
            for (int j = 0; j < 6; j++) {
                // 把得到的值与 0x0000003D 进行位与运算，取得字符数组 chars 索引
                long index = 0x0000003D & lHexLong;
                // 把取得的字符相加
                outChars += chars[(int) index];
                // 每次循环按位右移 5 位
                lHexLong = lHexLong >> 5;
            }
            // 把字符串存入对应索引的输出数组
            resUrl[i] = outChars;
        }
        return resUrl;
    }

    /**
     * 短网址生成方法
     * @param url
     * @return
     */
    public static String getShortUrl(String url){
        return  getShortUrl(url, "H202212101126!UUID@Hymn.com");
    }
    /**
     * 短网址生成方法
     * @param url
     * @param setKey
     * @return
     */
    public static String getShortUrl(String url, String setKey){
        String[] aResult = shortUrl(url, setKey);
        return aResult[0] + aResult[1];
    }

    /**
     * BASE64解码返回byte
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryptBASE64(String key)  {
        // 将Base64编码转为byte数组
        byte [] base64Bytes = Base64.getDecoder().decode(key);
        // 将Byte数组转为String,返回结果 return new String(base64Bytes);
        return base64Bytes;
    }

    /**
     * BASE64编码返回字符串
     * @param key
     * @return
     */
    public static String encryptBASE64(byte[] key) {
        // 将源字符串转为byte数组 byte [] strBytes = str.getBytes();
        // 链式调用,返回结果
        return Base64.getEncoder().encodeToString(key);
    }

    /**
     * 生成对应密钥
     * @param algName 指定加密算法[RSA/ECDSA]
     * @return  JwtKeyPair 如何新构建要调用initBuildKey
     */
    public static JwtKeyPair generaJwtKey(String algName) {
        String keyId = UUID.randomUUID().toString();
        switch (algName) {
            case "RSA":
                return new JwtRSAKey(keyId, 3072);
            case "ECDSA":
                return new JwtECDSAKey(keyId, 256,"secp256k1");
            default: {
                log.warn("指定的加密类型不支持。只支持[RSA/ECDSA]");
                return null;
            }
        }
    }

    /**
     * 从文件读入公/私钥
     * @param pubFile
     * @param priFile
     * @return
     */
    public static JwtKeyPair readFileJwtKeyPair(File pubFile, File priFile, String algName) throws IOException {
        // 从 公钥保存的文件 读取 公钥的Base64文本
        String pubKeyBase64 = FileUtils.readFileToString(pubFile, "UTF-8");
        //读取私钥：
        // 从 私钥保存的文件 读取 私钥的base文本
        String priKeyBase64 = FileUtils.readFileToString(priFile, "UTF-8");

        return readJwtKeyPair(pubKeyBase64, priKeyBase64, algName);
    }

    /**
     * 从证书串读入公/私钥
     * @param pubKeyBase64
     * @param priKeyBase64
     * @param algName 指定加密算法[RSA/ECDSA]
     * @return
     */
    public static JwtKeyPair readJwtKeyPair(String pubKeyBase64,String priKeyBase64, String algName)  {
        JwtKeyPair keyEty = generaJwtKey(algName);
        try {
            if (keyEty == null) {
                throw new NoSuchAlgorithmException("加密算法无效，需要指定[RSA/ECDSA]。");
            }
            // 获取指定算法的密钥工厂, 根据 已编码的公钥规格, 生成公钥对象
            PublicKey pubKey = keyEty.reaPubKey(pubKeyBase64);

            //读取私钥：
            // 获取指定算法的密钥工厂, 根据 已编码的私钥规格, 生成私钥对象
            PrivateKey priKey = keyEty.reaPriKey(priKeyBase64);

            keyEty.setPublicKey(pubKey);
            keyEty.setPrivateKey(priKey);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            log.error("读入解析{}公/私钥,密钥数据错误。",algName, e);
        }

        return keyEty;
    }

    /**
     * 公钥加密数据，RSA 非对称加密在使用中通常公钥公开，私钥保密，使用公钥加密，私钥解密
     */
    public static byte[] encryptData(byte[] plainData, JwtKeyPair pubKey) throws Exception {
        // 获取指定算法的密码器
        Cipher cipher = Cipher.getInstance(pubKey.getAlgSignatureName());

        // 初始化密码器（公钥加密模型）
        cipher.init(Cipher.ENCRYPT_MODE, pubKey.getPublicKey());

        // 加密数据, 返回加密后的密文
        return cipher.doFinal(plainData);
    }

    /**
     * 私钥解密数据
     */
    public static byte[] decryptData(byte[] cipherData, JwtKeyPair priKey) throws Exception {
        // 获取指定算法的密码器
        Cipher cipher = Cipher.getInstance(priKey.getAlgSignatureName());

        // 初始化密码器（私钥解密模型）
        cipher.init(Cipher.DECRYPT_MODE, priKey.getPrivateKey());

        // 解密数据, 返回解密后的明文
        return cipher.doFinal(cipherData);
    }

}
